#pragma once

#include <cmath>

#include "base.h"

#include "../updater/ma.h"
#include "../updater/minmax.h"

#include "scale.h"

namespace mdtx::online::indicator
{
    /**
     * @brief Accumulation  distribution
     *
     * @details ad = SUM(vol * ((close - low) - (high - close)) / (high - low))
     *
     * @tparam T
     */
    template <typename T>
    class ad : public indi_base<T, T, 4, 1>
    {
        using base = indi_base<T, T, 4, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        T sum;
        kahan<T> acc_sum;

    public:
        /**
         * @brief update
         *
         * @param in high, low, close, vol
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low, close, vol] = in;
            if (high != low)
            {
                acc_sum(sum, vol * ((close - low) - (high - close)) / (high - low));
            }
            return tuple_out(sum);
        }
    };

    /**
     * @brief AD oscillator / Chaikin Oscillator
     *
     * @details adosc = EMA_fast(ad) - EMA_slow(ad)
     *
     * @tparam T
     */
    template <typename T>
    class adosc : public indi_base<T, T, 4, 1>
    {
        using base = indi_base<T, T, 4, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        impl::ema<T> mfast, mslow;

    public:
        explicit adosc(ptrdiff_t fast_period = 3, ptrdiff_t slow_period = 10)
            : mfast(T(2) / (std::min(fast_period, slow_period) - 1)),
              mslow(T(2) / (std::max(fast_period, slow_period) - 1))
        {
        }

        /**
         * @brief update
         *
         * @param in high, low, close, vol
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low, close, vol] = in;
            T ad{};
            if (high != low)
            {
                ad = vol * ((close - low) - (high - close)) / (high - low);
            }
            mfast.insert_impl(ad);
            mslow.insert_impl(ad);
            return tuple_out(mfast.m - mslow.m);
        }
    };

    /**
     * @brief Average True Range
     *
     * @details atr = EMA(tr)
     *
     * @tparam T
     */
    template <typename T>
    class atr : public indi_base<T, T, 3, 1>
    {
        using base = indi_base<T, T, 3, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        impl::ema<T> ma;
        T last{};

    public:
        explicit atr(ptrdiff_t period = 14)
            : ma(T(1) / period)
        {
        }

        /**
         * @brief update
         *
         * @param in high, low, close
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low, close] = in;

            T tr;
            if (ma.init)
            {
                tr = std::max(std::max(high - low, std::abs(high - last)), std::abs(low - last));
            }
            else
            {
                tr = high - low;
            }
            ma.insert_impl(tr);
            last = close;

            return tuple_out(ma.m);
        }
    };

    /**
     * @brief Directional Movement
     *
     * @tparam T
     */
    template <typename T>
    class dm : public indi_base<T, T, 2, 2>
    {
        using base = indi_base<T, T, 2, 2>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        T last_high{}, last_low{};
        impl::ema<T> mup, mdown;
        bool init{};

    public:
        explicit dm(ptrdiff_t period = 14)
            : mup(T(1) / period),
              mdown(T(1) / period)
        {
        }

        /**
         * @brief update
         *
         * @param in high, low
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low] = in;

            if (init)
            {
                T up{}, down{};
                T dh = high - last_high;
                T dl = last_low - low;
                if (dh >= 0 && dh >= dl)
                {
                    up = dh;
                }
                if (dl >= 0 && dl >= dh)
                {
                    down = dl;
                }
                mup.insert_impl(up);
                mdown.insert_impl(down);
            }
            else
            {
                init = true;
            }
            last_high = high;
            last_low = low;

            return tuple_out(mup.m, mdown.m);
        }
    };

    /**
     * @brief Direction Index
     *
     * @tparam T
     */
    template <typename T>
    class di : public indi_base<T, T, 3, 2>
    {
        using base = indi_base<T, T, 3, 2>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        atr<T> _atr;
        dm<T> _dm;

    public:
        explicit di(ptrdiff_t period = 14)
            : _atr(period),
              _dm(period)
        {
        }

        /**
         * @brief update
         *
         * @param in high, low, close
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low, close] = in;

            auto [m] = _atr.update(in);
            auto [dm_up, dm_down] = _dm.update(std::make_tuple(high, low));
            if (m == 0)
            {
                m = small_number(m);
            }

            return tuple_out(dm_up / m, dm_down / m);
        }
    };

    /**
     * @brief Directional Movement Index
     *
     * @tparam T
     */
    template <typename T>
    class dx : public indi_base<T, T, 3, 1>
    {
        using base = indi_base<T, T, 3, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        di<T> _di;

    public:
        explicit dx(ptrdiff_t period = 14)
            : _di(period)
        {
        }

        /**
         * @brief update
         *
         * @param in high, low, close
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [di_up, di_down] = _di.update(in);
            T diff = std::abs(di_up - di_down);
            T sum = di_up + di_down;
            if (sum == 0)
            {
                sum = small_number(sum);
            }
            return tuple_out(diff / sum);
        }
    };

    /**
     * @brief Average Directional Movement Index
     *
     * @tparam T
     */
    template <typename T>
    class adx : public indi_base<T, T, 3, 1>
    {
        using base = indi_base<T, T, 3, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        impl::ema<T> ma;
        dx<T> _dx;

    public:
        explicit adx(ptrdiff_t period = 14)
            : ma(T(1) / period),
              _dx(period)
        {
        }

        /**
         * @brief update
         *
         * @param in high, low, close
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [val] = _dx.update(in);
            ma.insert_impl(val);
            return tuple_out(ma.m);
        }
    };

    /**
     * @brief ADX rating
     *
     * @tparam T
     */
    template <typename T>
    class adxr : public indi_base<T, T, 3, 1>
    {
        using base = indi_base<T, T, 3, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf;
        adx<T> _adx;

    public:
        explicit adxr(ptrdiff_t period = 14)
            : buf(period),
              _adx(period)
        {
        }

        /**
         * @brief update
         *
         * @param in high, low, close
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [val] = _adx.update(in);
            buf.push_back(val);
            return tuple_out((buf.front() + buf.back()) / 2);
        }
    };

    /**
     * @brief Awesome Oscillator
     *
     * @details ao = SMA_fast(hl) - SMA_slow(hl), hl = (high + low) / 2
     *
     * @tparam T
     */
    template <typename T>
    class ao : public indi_base<T, T, 2, 1>
    {
        using base = indi_base<T, T, 2, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        ptrdiff_t nfast, nslow;
        boost::circular_buffer<T> buf;
        impl::sma<T> mfast{}, mslow{};

    public:
        ao(ptrdiff_t fast_period = 5, ptrdiff_t slow_period = 34)
            : nfast(std::min(fast_period, slow_period)),
              nslow(std::max(fast_period, slow_period)),
              buf(nslow)
        {
        }

        /**
         * @brief update
         *
         * @param in high, low
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            T hl = (std::get<0>(in) + std::get<1>(in)) / 2;
            if (buf.full())
            {
                mfast.roll_impl(hl, buf[nfast - 1]);
                mslow.roll_impl(hl, buf.back());
            }
            else if (buf.size() >= nfast)
            {
                mfast.roll_impl(hl, buf[nfast - 1]);
                mslow.insert_impl(hl);
            }
            else
            {
                mfast.insert_impl(hl);
                mslow.insert_impl(hl);
            }
            buf.push_back(hl);

            return tuple_out(mfast.m - mslow.m);
        }
    };

    /**
     * @brief Absolute Price Oscillator
     *
     * @details apo = EMA_fast(x) - EMA_slow(x)
     *
     * @tparam T
     */
    template <typename T>
    class apo : public indi_unary1<T>
    {
        using base = indi_unary1<T>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        impl::ema<T> mfast, mslow;

    public:
        apo(ptrdiff_t fast_period = 12, ptrdiff_t slow_period = 26)
            : mfast(T(2) / (std::min(fast_period, slow_period) - 1)),
              mslow(T(2) / (std::max(fast_period, slow_period) - 1))
        {
        }

        /**
         * @brief update
         *
         * @param in price
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [x] = in;
            mfast.insert_impl(x);
            mslow.insert_impl(x);
            return tuple_out(mfast.m - mslow.m);
        }
    };

    /**
     * @brief Aroon Indicator
     *
     * @details aroon = 100 * (N - argmin/argmax) / N
     *
     * @tparam T
     */
    template <typename T>
    class aroon : public indi_base<T, T, 2, 2>
    {
        using base = indi_base<T, T, 2, 2>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf;
        impl::argminmax<T> mm{};
        ptrdiff_t n;

    public:
        explicit aroon(ptrdiff_t period = 25)
            : buf(period * 2),
              n(period)
        {
        }

        /**
         * @brief update
         *
         * @param in high, low
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low] = in;

            if (buf.full())
            {
                mm.remove_impl(buf[0]);
                mm.remove_impl(buf[1]);
            }
            mm.insert_impl(high);
            mm.insert_impl(low);
            bpush(buf, high, low);

            auto tmin = mm.minidx.front();
            auto tmax = mm.maxidx.front();
            T up = (n - tmax) / T(n) * 100;
            T down = (n - tmin) / T(n) * 100;

            return tuple_out(up, down);
        }
    };

    /**
     * @brief Aroon Oscillator
     *
     * @details aroonosc = aroon_up - aroon_down
     *
     * @tparam T
     */
    template <typename T>
    class aroonosc : public indi_base<T, T, 2, 1>
    {
        using base = indi_base<T, T, 2, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        aroon<T> _aroon;

    public:
        explicit aroonosc(ptrdiff_t period = 25)
            : _aroon(period)
        {
        }

        /**
         * @brief update
         *
         * @param in high, low
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [up, down] = _aroon.update(in);
            return tuple_out(up - down);
        }
    };

    /**
     * @brief Balance of Power
     *
     * @details bop = (close - open) / (high - low)
     *
     * @tparam T
     */
    template <typename T>
    class bop : public indi_base<T, T, 4, 1>
    {
        using base = indi_base<T, T, 4, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    public:
        /**
         * @brief update
         *
         * @param in open, high, low, close
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [open, high, low, close] = in;

            T delta = high - low;
            if (delta == 0)
            {
                delta = small_number(delta);
            }
            return tuple_out((close - open) / delta);
        }
    };

    /**
     * @brief Commodity Channel Index
     *
     * @details cci = (typical_price - sma) / (0.015 * mae)
     *
     * @tparam T
     */
    template <typename T>
    class cci : public indi_base<T, T, 3, 1>
    {
        using base = indi_base<T, T, 3, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        mae<T> _mae;

    public:
        explicit cci(ptrdiff_t period = 20)
            : _mae(period)
        {
        }

        /**
         * @brief update
         *
         * @param in high, low, close
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low, close] = in;

            T typ_price = (high + low + close) / 3, val{};
            auto [m, s] = _mae.update(std::make_tuple(typ_price));
            if (s != 0)
            {
                val = (typ_price - m) / (0, 015 * s);
            }

            return tuple_out(val);
        }
    };

    /**
     * @brief Chandelier Exits
     *
     * @details ce = (low + multi * atr, high - multi * atr)
     *
     * @tparam T
     */
    template <typename T>
    class ce : public indi_base<T, T, 3, 2>
    {
        using base = indi_base<T, T, 3, 2>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        atr<T> _atr;
        boost::circular_buffer<T> buf;
        impl::minmax<T> mm{};
        T multi;

    public:
        explicit ce(ptrdiff_t period = 22, T multi = 3)
            : _atr(period),
              buf(period * 2),
              multi(multi)
        {
        }

        /**
         * @brief update
         *
         * @param in high, low, close
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low, close] = in;

            auto [val] = _atr.update(in);
            if (buf.full())
            {
                mm.remove_impl(buf[0], buf[1]);
            }
            mm.insert_impl(high, low);
            bpush(buf, high, low);

            high = mm.maxdeq.front();
            low = mm.mindeq.front();
            val *= multi;
            return tuple_out(low + val, high - val);
        }
    };

    /**
     * @brief Chande Momentum Oscillator
     *
     * @details cmo = (sum_up - sum_down) / (sum_up + sum_down)
     *
     * @tparam T
     */
    template <typename T>
    class cmo : public indi_unary1<T>
    {
        using base = indi_unary1<T>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf;
        impl::sum<T> sup{}, sdown{};
        T last{};
        bool init{};

    public:
        explicit cmo(ptrdiff_t period = 20)
            : buf(period * 2)
        {
        }

        /**
         * @brief update
         *
         * @param in price
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            T x = std::get<0>(in);

            if (init)
            {
                T up{}, down{};
                if (x > last)
                {
                    up = x - last;
                }
                else if (x < last)
                {
                    down = last - x;
                }
                if (buf.full())
                {
                    sup.roll_impl(up, buf[0]);
                    sdown.roll_impl(down, buf[1]);
                }
                else
                {
                    sup.insert_impl(up);
                    sdown.insert_impl(down);
                }
                bpush(buf, up, down);
            }
            else
            {
                init = true;
            }
            last = x;

            T val{};
            if (sup.s != sdown.s)
            {
                val = (sup.s - sdown.s) / (sup.s + sdown.s) * 100;
            }
            return tuple_out(val);
        }
    };

    /**
     * @brief Chaikin Money Flow
     *
     * @details volume weighted moving average of CLV
     *
     * @tparam T
     */
    template <typename T>
    class cmf : public indi_base<T, T, 4, 1>
    {
        using base = indi_base<T, T, 4, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf;
        impl::wsma<T> ma{};

    public:
        explicit cmf(ptrdiff_t period = 21)
            : buf(period * 2)
        {
        }

        /**
         * @brief update
         *
         * @param in high, low, close, vol
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low, close, vol] = in;

            T CLV{};
            if (high != low)
            {
                CLV = ((close - low) - (high - close)) / (high - low);
            }
            if (buf.full())
            {
                ma.remove_impl(buf[0], buf[1]);
            }
            ma.insert_impl(CLV, vol);
            bpush(buf, CLV, vol);
        }
    };

    /**
     * @brief Comparative performance
     *
     * @details compf = 1 + ((a / b) - (a0 / b0)) / (a0 / b0)
     *
     * @tparam T
     */
    template <typename T>
    class compf : public indi_base<T, T, 2, 1>
    {
        using base = indi_base<T, T, 2, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf;

    public:
        explicit compf(ptrdiff_t period)
            : buf(period)
        {
        }

        /**
         * @brief update
         *
         * @param in var_a, var_b
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            buf.push_back(std::get<0>(in) / std::get<1>(in));
            T val = 1;
            if (buf.full())
            {
                val = 1 + (buf.back() - buf.front()) / buf.front();
            }

            return tuple_out(val);
        }
    };

    /**
     * @brief Chaikin Volatility
     *
     * @details cvi = 100 * (ema - ema0) / ema0, ema = EMA(high - low)
     *
     * @tparam T
     */
    template <typename T>
    class cvi : public indi_base<T, T, 2, 1>
    {
        using base = indi_base<T, T, 2, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf;
        impl::ema<T> ma;

    public:
        explicit cvi(ptrdiff_t period = 10)
            : buf(period),
              ma(T(2) / (period + 1))
        {
        }

        /**
         * @brief update
         *
         * @param in high, low
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            T hl = (std::get<0>(in) + std::get<1>(in)) / 2;
            if (hl == 0)
            {
                // in case of limit
                hl = small_number(hl);
            }
            ma.insert_impl(hl);
            T val = (ma.m - buf.front()) / buf.front() * 100;
            buf.push_back(ma.m);
            return tuple_out(val);
        }
    };

    /**
     * @brief Dispersion
     *
     * @details disp = SQRT(SMA(ABS(log_return)))
     *
     * @tparam T
     */
    template <typename T>
    class disp : public indi_unary1<T>
    {
        using base = indi_unary1<T>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        T last{};
        boost::circular_buffer<T> buf;
        impl::sma<T> ma{};
        bool init{};

    public:
        explicit disp(ptrdiff_t period)
            : buf(period)
        {
        }

        /**
         * @brief update
         *
         * @param in price
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            T x = std::log(std::get<0>(in));

            if (init)
            {
                T ret = std::abs(x - last);
                if (buf.full())
                {
                    ma.roll_impl(x, buf.front());
                }
                else
                {
                    ma.insert_impl(x);
                }
                buf.push_back(ret);
            }
            else
            {
                init = true;
            }
            last = x;

            return tuple_out(std::sqrt(ma.m));
        }
    };

    /**
     * @brief Detrended Price Oscillator
     *
     * @details dpo = ma - ma_lagged, lag = period / 2
     *
     * @tparam T
     */
    template <typename T>
    class dpo : public indi_unary1<T>
    {
        using base = indi_unary1<T>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf;
        impl::sma<T> ma{};
        ptrdiff_t midx;

    public:
        explicit dpo(ptrdiff_t period = 20)
            : buf(period),
              midx(period / 2)
        {
        }

        tuple_out update(tuple_in const &in) override
        {
            T x = std::get<0>(in);

            if (buf.full())
            {
                ma.roll_impl(x, buf.front());
            }
            else
            {
                ma.insert_impl(x);
            }
            buf.push_back(x);

            T lag = buf.full() ? buf[midx] : buf[buf.size() / 2];
            return tuple_out(lag - ma.m);
        }
    };

    /**
     * @brief Efficiency Ratio
     *
     * @details effr = SUM(GAIN(x, n)) / SUM(ABS(GAIN(x, 1)))
     *
     * @tparam T
     */
    template <typename T>
    class effr : public indi_unary1<T>
    {
        using base = indi_unary1<T>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf;
        impl::sum<T> d1sum{};
        bool init{};

    public:
        explicit effr(ptrdiff_t period)
            : buf(period * 2)
        {
        }

        /**
         * @brief update
         *
         * @param in price
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            T x = std::get<0>(in), d1{}, r{};
            if (init)
            {
                d1 = std::abs(x - buf.back());
                if (buf.full())
                {
                    d1sum.roll_impl(d1, buf.front());
                }
                else
                {
                    d1sum.insert_impl(d1);
                }
                r = (x - buf[1]) / d1sum.s;
            }
            else
            {
                init = true;
            }
            bpush(buf, d1, x);

            return tuple_out(r);
        }
    };

    /**
     * @brief Ease of Movement
     *
     * @details emv = hl_gain / (vol * vscale / (high - low)), hl = (high + low) / 2
     *
     * @tparam T
     */
    template <typename T>
    class emv : public indi_base<T, T, 3, 1>
    {
        using base = indi_base<T, T, 3, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        T last{}, vscale;
        bool init{};

    public:
        explicit emv(T vol_scale = 0.0001)
            : vscale(vol_scale)
        {
        }

        /**
         * @brief update
         *
         * @param in high, low, vol
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low, vol] = in;

            T hl = (high + low) / 2, delta = high - low, val{};
            if (init)
            {
                if (delta != 0 && vol != 0)
                {
                    val = (hl - last) / (vol * vscale / delta);
                }
                else
                {
                    val = 0;
                }
            }
            else
            {
                init = true;
            }
            last = hl;

            return tuple_out(val);
        }
    };

    template <typename T>
    class fisher : public indi_base<T, T, 2, 1>
    {
        using base = indi_base<T, T, 2, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf;
        impl::minmax<T> mm{};
        impl::ema<T> m1, m2;

    public:
        explicit fisher(ptrdiff_t period)
            : buf(period),
              m1(T(1) / 3), m2(T(1) / 2)
        {
        }

        /**
         * @brief update
         *
         * @param in high, low
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low] = in;

            T tp = (high + low) / 2;
            if (buf.full())
            {
                mm.remove_impl(buf.front());
            }
            mm.insert_impl(tp);
            buf.push_back(tp);

            if (buf.full())
            {
                T min = mm.mindeq.front();
                T max = mm.maxdeq.front();
                T delta = max - min;
                if (delta == 0)
                {
                    // max == min -> tp == min == max
                    m1.insert_impl(-0.5);
                }
                else
                {
                    m1.insert_impl((tp - min) / delta - 0.5);
                }
                T m = m1.m;
                if (m > 1)
                {
                    m = 1 - small_number(m);
                }
                else if (m < -1)
                {
                    m = -1 + small_number(m);
                }
                m2.insert_impl(std::log((1 + m) / (1 - m)));
            }

            return tuple_out(m2.m);
        }
    };

    /**
     * @brief Market Facilitation Index
     *
     * @details mktfi = (high - low) / vol
     *
     * @tparam T
     */
    template <typename T>
    class mktfi : public indi_base<T, T, 3, 1>
    {
        using base = indi_base<T, T, 3, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    public:
        /**
         * @brief update
         *
         * @param in high, low, vol
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low, vol] = in;
            return tuple_out((high - low) / vol);
        }
    };

    /**
     * @brief MASS index
     *
     * @details mass = SUM(EMA(EMA(hl)) / EMA(hl)), hl = (high - low)
     *
     * @tparam T
     */
    template <typename T>
    class mass : public indi_base<T, T, 2, 1>
    {
        using base = indi_base<T, T, 2, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf;
        impl::xema<T, 2> ma;
        impl::sum<T> s{};

    public:
        explicit mass(ptrdiff_t period = 25, ptrdiff_t exp_period = 9)
            : buf(period),
              ma(T(2) / (exp_period + 1))
        {
        }

        /**
         * @brief update
         *
         * @param in high, low
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low] = in;

            T e{};
            ma.insert_impl(high - low);
            if (ma.m[1] != 0)
            {
                e = ma.m[0] / ma.m[1];
            }

            if (buf.full())
            {
                s.roll_impl(e, buf.front());
            }
            else
            {
                s.insert_impl(e);
            }
            buf.push_back(e);

            return tuple_out(s.s);
        }
    };

    /**
     * @brief Money Flow Index
     *
     * @details mfi = 100 * (tnvr_up - tnvr_down) / (tnvr_up + tnvr_down)
     *
     * @tparam T
     */
    template <typename T>
    class mfi : public indi_base<T, T, 2, 1>
    {
        using base = indi_base<T, T, 2, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf;
        impl::sum<T> sup{}, sdown{};
        T last{};
        bool init{};

    public:
        explicit mfi(ptrdiff_t period = 14)
            : buf(period * 2)
        {
        }

        /**
         * @brief update
         *
         * @param in price, turnover
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [p, tnvr] = in;

            if (init)
            {
                T tnvr_up{}, tnvr_down{};
                if (p > last)
                {
                    tnvr_up = tnvr;
                }
                else if (p < last)
                {
                    tnvr_down = tnvr;
                }
                if (buf.full())
                {
                    sup.roll_impl(tnvr_up, buf[0]);
                    sdown.roll_impl(tnvr_down, buf[1]);
                }
                else
                {
                    sup.insert_impl(tnvr_up);
                    sdown.insert_impl(tnvr_down);
                }
                bpush(buf, tnvr_up, tnvr_down);
            }
            else
            {
                init = true;
            }
            last = p;

            T sum = sup.s + sdown.s, val{};
            if (sum != 0)
            {
                val = (sup.s - sdown.s) / sum * 100;
            }
            return tuple_out(val);
        }
    };

    /**
     * @brief On Balance Volume
     *
     * @details obv = CUMSUM(SIGN(gain) * vol), resets when gain = 0
     *
     * @tparam T
     */
    template <typename T>
    class obv : public indi_base<T, T, 2, 1>
    {
        using base = indi_base<T, T, 2, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        T vacc, last;
        bool init;

    public:
        /**
         * @brief update
         *
         * @param in price, vol
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [p, v] = in;

            if (init)
            {
                if (p == last)
                {
                    vacc = 0;
                }
                else if (p < last)
                {
                    vacc -= v;
                }
                else
                {
                    vacc += v;
                }
            }
            else
            {
                init = true;
            }
            last = p;

            return tuple_out(vacc);
        }
    };

    /**
     * @brief Donchian Channel
     *
     * @details donchian = (HIGH(high), LOW(low))
     *
     * @tparam T
     */
    template <typename T>
    class donchian : public indi_base<T, T, 2, 2>
    {
        using base = indi_base<T, T, 2, 2>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf;
        impl::minmax<T> mm{};

    public:
        explicit donchian(ptrdiff_t period)
            : buf(period * 2)
        {
        }

        /**
         * @brief update
         *
         * @param in high, low
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low] = in;

            if (buf.full())
            {
                mm.remove_impl(buf[0], buf[1]);
            }
            mm.insert_impl(high, low);
            bpush(buf, high, low);

            return tuple_out(mm.maxdeq.front(), mm.mindeq.front());
        }
    };

    /**
     * @brief Prabolic SAR
     *
     * @tparam T
     */
    template <typename T>
    class psar : public indi_base<T, T, 2, 2>
    {
        using base = indi_base<T, T, 2, 2>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        T acc{}, acc0, accmax, sar{}, ep{};
        bool init{}, uptrend{true};

    public:
        psar(T acc_init = 0.02, T acc_max = 0.2)
            : acc0(acc_init), accmax(acc_max)
        {
        }

        /**
         * @brief update
         *
         * @param in high, low
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low] = in;

            if (init)
            {
                if (uptrend)
                {
                    if (low < sar)
                    {
                        uptrend = false;
                        acc = 0;
                        sar = std::exchange(ep, low);
                    }
                    else
                    {
                        ep = std::max(ep, high);
                        acc = std::min(acc + acc0, accmax);
                        sar += acc * (ep - sar);
                    }
                }
                else
                {
                    if (high > sar)
                    {
                        uptrend = true;
                        acc = 0;
                        sar = std::exchange(ep, low);
                    }
                    else
                    {
                        ep = std::min(ep, low);
                        acc = std::min(acc + acc0, accmax);
                        sar += acc * (ep - sar);
                    }
                }
            }
            else
            {
                // always assume uptrend init
                sar = low;
                ep = high;
                init = true;
            }

            return tuple_out(sar, uptrend ? 1 : -1);
        }
    };

    /**
     * @brief Price and Volume Trend
     *
     * @details pvt = SUM(volume * (price - last) / last)
     *
     * @tparam T
     */
    template <typename T>
    class pvt : public indi_base<T, T, 2, 1>
    {
        using base = indi_base<T, T, 2, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        impl::sum<T> s;
        T last;
        bool init;

    public:
        /**
         * @brief update
         *
         * @param in price, vol
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [p, v] = in;

            if (init)
            {
                s.insert_impl(v * ((p - last) / last));
            }
            else
            {
                init = true;
            }
            last = p;

            return tuple_out(s.s);
        }
    };

    /**
     * @brief Qstick
     *
     * @details qtick = SMA(open - close)
     *
     * @tparam T
     */
    template <typename T>
    class qstick : public indi_base<T, T, 2, 1>
    {
        using base = indi_base<T, T, 2, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf;
        impl::sma<T> ma{};

    public:
        explicit qstick(ptrdiff_t period)
            : buf(period)
        {
        }

        /**
         * @brief update
         *
         * @param in open, close
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            T d = std::get<0>(in) - std::get<1>(in);
            if (buf.full())
            {
                ma.roll_impl(d, buf.front());
            }
            else
            {
                ma.insert_impl(d);
            }
            buf.push_back(d);

            return tuple_out(ma.m);
        }
    };

    /**
     * @brief Stochastic Momentum Index
     *
     * @tparam T
     */
    template <typename T>
    class smi : public indi_base<T, T, 3, 1>
    {
        using base = indi_base<T, T, 3, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf;
        impl::minmax<T> mm{};
        impl::xema<T, 2> m_cm, m_hl;
        impl::ema<T> ma;

    public:
        explicit smi(ptrdiff_t period = 14, ptrdiff_t cm_period = 3, ptrdiff_t hl_period = 5)
            : buf(period * 2),
              m_cm(T(2) / (cm_period + 1)),
              m_hl(T(2) / (hl_period + 1)),
              ma(T(2) / (period + 1))
        {
        }

        /**
         * @brief update
         *
         * @param in high, low, close
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low, close] = in;

            if (buf.full())
            {
                mm.remove_impl(buf[0], buf[1]);
            }
            mm.insert_impl(high, low);
            bpush(buf, high, low);

            high = mm.maxdeq.front();
            low = mm.mindeq.front();
            m_cm.insert_impl(close - (high + low) / 2);
            m_hl.insert_impl(high - low);

            T signal{};
            if (m_hl.m[1] != 0)
            {
                signal = (m_cm.m[1] / m_hl.m[1]) * 200;
            }
            ma.insert_impl(signal);

            return tuple_out(ma.m);
        }
    };

    /**
     * @brief Stochastic Oscillator
     *
     * @details stosc = SMA(SMA(fast_k)), fast_k = 100 * (close - min) / (max - min)
     *
     * @tparam T
     */
    template <typename T>
    class stosc : public indi_base<T, T, 3, 2>
    {
        using base = indi_base<T, T, 3, 2>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf, kbuf, dbuf;
        impl::minmax<T> mm{};
        impl::sma<T> mk{}, md{};

    public:
        stosc(ptrdiff_t period = 9, ptrdiff_t k_period = 5, ptrdiff_t d_period = 3)
            : buf(period * 2),
              kbuf(k_period),
              dbuf(d_period)
        {
        }

        /**
         * @brief update
         *
         * @param in high, low, close
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low, close] = in;

            if (buf.full())
            {
                mm.remove_impl(buf[0], buf[1]);
            }
            mm.insert_impl(high, low);
            bpush(buf, high, low);

            high = mm.maxdeq.front();
            low = mm.mindeq.front();
            T delta = high - low, fastk;
            if (delta != 0)
            {
                fastk = (close - low) / (high - low) * 100;
            }
            else
            {
                fastk = 50;
            }

            if (kbuf.full())
            {
                mk.roll_impl(fastk, kbuf.front());
            }
            else
            {
                mk.insert_impl(fastk);
            }
            kbuf.push_back(fastk);

            if (dbuf.full())
            {
                md.roll_impl(mk.m, dbuf.front());
            }
            else
            {
                md.insert_impl(mk.m);
            }
            dbuf.push_back(mk.m);

            return tuple_out(mk.m, md.m);
        }
    };

    /**
     * @brief KDJ, essentially stosc
     *
     * @tparam T
     */
    template <typename T>
    class kdj : public indi_base<T, T, 3, 3>
    {
        using base = indi_base<T, T, 3, 3>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf;
        impl::minmax<T> mm{};
        impl::ema<T> mk, md;

    public:
        kdj(ptrdiff_t period = 9, ptrdiff_t k_period = 3, ptrdiff_t d_period = 3)
            : buf(period * 2),
              mk(T(2) / (k_period + 1)),
              md(T(2) / (d_period + 1))
        {
        }

        /**
         * @brief update
         *
         * @param in high, low, close
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low, close] = in;

            if (buf.full())
            {
                mm.remove_impl(buf[0], buf[1]);
            }
            mm.insert_impl(high, low);
            bpush(buf, high, low);

            high = mm.maxdeq.front();
            low = mm.mindeq.front();
            T delta = high - low, fastk;
            if (delta != 0)
            {
                fastk = (close - low) / (high - low) * 100;
            }
            else
            {
                fastk = 50;
            }

            mk.insert_impl(fastk);
            md.insert_impl(mk.m);

            T j = 3 * md.m - 2 * mk.m;
            return tuple_out(mk.m, md.m, j);
        }
    };

    /**
     * @brief TRIX Indicator
     *
     * @tparam T
     */
    template <typename T>
    class trix : public indi_unary1<T>
    {
        using base = indi_unary1<T>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        impl::xema<T, 3> ma;
        T last{};

    public:
        explicit trix(ptrdiff_t period = 12)
            : ma(T(2) / (period + 1))
        {
        }

        /**
         * @brief update
         *
         * @param in price
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            T x = std::get<0>(in), val{};
            if (ma.init)
            {
                ma.insert(x);
                val = (ma.m[2] - last) / ma.m[2];
            }
            else
            {
                ma.insert(x);
            }
            last = ma.m[2];

            return tuple_out(val);
        }
    };

    /**
     * @brief True Strength Indicator
     *
     * @details EMA(EMA(gain)) / EMA(EMA(ABS(gain)))
     *
     * @tparam T
     */
    template <typename T>
    class tsi : public indi_unary1<T>
    {
        using base = indi_unary1<T>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        impl::xema<T, 2> ma, ma_abs;
        T last{};
        bool init{};

    public:
        explicit tsi(ptrdiff_t period = 14)
            : ma(T(2) / (period + 1)),
              ma_abs(T(2) / (period + 1))
        {
        }

        /**
         * @brief update
         *
         * @param in price
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            if (init)
            {
                T delta = std::get<0>(in) - last;
                ma.insert_impl(delta);
                ma_abs.insert_impl(std::abs(delta));
            }
            else
            {
                init = true;
            }
            last = std::get<0>(in);

            T val{};
            if (ma_abs.m[1] != 0)
            {
                val = ma.m[1] / ma_abs.m[1];
            }
            return tuple_out(val);
        }
    };

    /**
     * @brief Ulcer Index
     *
     * @details ulcer = SMA(dev), dev = (x / max - 1)^2
     *
     * @tparam T
     */
    template <typename T>
    class ulcer : public indi_unary1<T>
    {
        using base = indi_unary1<T>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf;
        impl::extrema<T, std::greater<T>> max{};
        impl::sma<T> ma{};

    public:
        explicit ulcer(ptrdiff_t period = 14)
            : buf(period * 2)
        {
        }

        tuple_out update(tuple_in const &in) override
        {
            T x = std::get<0>(in);

            max.insert_impl(x);
            if (buf.full())
            {
                max.remove_impl(buf[0]);
            }

            T r2 = std::pow(x / max.deq.front() - 1, 2);
            if (buf.full())
            {
                ma.roll_impl(r2, buf[1]);
            }
            else
            {
                ma.insert_impl(r2);
            }
            bpush(buf, x, r2);

            return tuple_out(ma.m);
        }
    };

    /**
     * @brief Vertical Horizontal Filter
     *
     * @details vhf = (min - max) / SUM(gain)
     *
     * @tparam T
     */
    template <typename T>
    class vhf : public indi_unary1<T>
    {
        using base = indi_unary1<T>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf;
        impl::minmax<T> mm{};
        impl::sum<T> s{};

    public:
        explicit vhf(ptrdiff_t period = 28)
            : buf(period * 2)
        {
        }

        /**
         * @brief update
         *
         * @param in price
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            T x = std::get<0>(in);
            T d = buf.empty() ? 0 : std::abs(x - buf.back());
            if (buf.full())
            {
                mm.remove_impl(buf[0]);
                s.roll_impl(d, buf[1]);
            }
            else
            {
                s.insert_impl(d);
            }
            mm.insert_impl(x);
            bpush(buf, x, d);

            T val{};
            if (s.s != 0)
            {
                T min = mm.mindeq.front();
                T max = mm.maxdeq.front();
                val = (max - min) / s.s;
            }

            return tuple_out(val);
        }
    };

    /**
     * @brief Positive/Negative volume index
     *
     * @tparam T
     */
    template <typename T>
    class volidx : public indi_base<T, T, 2, 2>
    {
        using base = indi_base<T, T, 2, 2>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        T pvi, nvi, lastp{}, lastv{};
        bool init{};

    public:
        explicit volidx(T init = 1000)
            : pvi(init), nvi(init)
        {
        }

        /**
         * @brief update
         *
         * @param in price, vol
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [p, v] = in;

            if (init)
            {
                if (lastv > v)
                {
                    pvi += pvi * ((p - lastp) / lastp);
                }
                else if (lastv < v)
                {
                    nvi += nvi * ((p - lastp) / lastp);
                }
            }
            else
            {
                init = true;
            }
            lastp = p;
            lastv = v;

            return tuple_out(pvi, nvi);
        }
    };

    /**
     * @brief Vortex Indicator
     *
     * @details vm_up = high - prev_low, vm_down = low - prev_high, Vortex = (SUM(vm_up) / SUM(TR), SUM(vm_down) / SUM(TR))
     *
     * @tparam T
     */
    template <typename T>
    class vortex : public indi_base<T, T, 3, 2>
    {
        using base = indi_base<T, T, 3, 2>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf;
        T prev_high{}, prev_low{}, prev_close{};
        impl::sum<T> s_tr{}, s_vm_up{}, s_vm_down{};
        bool init{};

    public:
        explicit vortex(ptrdiff_t period = 21)
            : buf(period * 3)
        {
        }

        /**
         * @brief update
         *
         * @param in high, low, close
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low, close] = in;

            if (init)
            {
                T tr = std::max(std::max(high - low, std::abs(high - prev_close)), std::abs(low - prev_close));
                T vm_up = high - prev_low;
                T vm_down = low - prev_high;
                if (buf.full())
                {
                    s_tr.roll_impl(tr, buf[0]);
                    s_vm_up.roll_impl(vm_up, buf[1]);
                    s_vm_down.roll_impl(vm_down, buf[2]);
                }
                else
                {
                    s_tr.insert_impl(tr);
                    s_vm_up.insert_impl(vm_up);
                    s_vm_down.insert_impl(vm_down);
                }
                bpush(buf, tr, vm_up, vm_down);
            }
            else
            {
                init = true;
            }
            prev_high = high;
            prev_low = low;
            prev_close = close;

            T up{}, down{};
            if (s_tr.s != 0)
            {
                up = s_vm_up.s / s_tr.s;
                down = s_vm_down.s / s_tr.s;
            }
            return tuple_out(up, down);
        }
    };

    /**
     * @brief Williams Accumulate Distribute
     *
     * @tparam T
     */
    template <typename T>
    class wad : public indi_base<T, T, 3, 1>
    {
        using base = indi_base<T, T, 3, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        T last, acc;
        bool init;

    public:
        /**
         * @brief update
         *
         * @param in high, low, close
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low, close] = in;

            T ad{};
            if (init)
            {
                if (close > last)
                {
                    ad = close - std::min(last, low);
                }
                else if (close < last)
                {
                    ad = close - std::max(last, high);
                }
            }
            else
            {
                init = true;
            }
            acc += ad;
            last = close;

            return tuple_out(acc);
        }
    };

    /**
     * @brief Williams R Percentage
     *
     * @details willr = -100 * (max - close) / (max - min)
     *
     * @tparam T
     */
    template <typename T>
    class willr : public indi_base<T, T, 3, 1>
    {
        using base = indi_base<T, T, 3, 1>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf;
        impl::minmax<T> mm{};
        T val{};

    public:
        explicit willr(ptrdiff_t period = 14)
            : buf(period * 2)
        {
        }

        /**
         * @brief update
         *
         * @param in high, low, close
         * @return tuple_out
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [high, low, close] = in;

            if (buf.full())
            {
                mm.remove_impl(buf[0], buf[1]);
            }
            mm.insert_impl(high, low);
            bpush(buf, high, low);

            high = mm.maxdeq.front();
            low = mm.mindeq.front();
            T delta = high - low, val{};
            if (delta != 0)
            {
                val = -(high - low) / delta * 100;
            }

            return tuple_out(val);
        }
    };
}
